import logging
import time
from threading import Thread

class GhostBreathingLedHelper:
    def __init__(self,color_table):
        color_table = {color[0]:color[1]  for color in color_table}
        self.color_table = sorted([[key,color_table[key]] for key in color_table],key=lambda x:x[0])


    def get_color(self,speed):
        target_color =  None
        if speed <= self.color_table[0][0]:
            target_color = self.color_table[0][1]
            return target_color

        for i,[anchor_speed,color] in enumerate(self.color_table):
            if speed <= anchor_speed:
                speed_1,speed_2 = self.color_table[i-1][0],anchor_speed
                scale = (speed-speed_1)/(speed_2-speed_1)
                target_color = []

                color_1,color_2 = self.color_table[i-1][1],color
                logging.info('color1:{} color2:{}'.format(color_1,color_2))
                for i,_ in enumerate(color_1):
                    c_1,c_2 = color_1[i],color_2[i]
                    if c_1 == c_2:
                        target_color.append(c_1)
                    else:
                        target_color.append(c_1+(c_2-c_1)*scale)
                return target_color

        target_color = self.color_table[-1][1]
        return target_color




class BreathingLed:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.gcode = self.printer.lookup_object('gcode')
        self.run_script = self.gcode.run_script

        self.printer.register_event_handler("klippy:connect", self._handle_connect)

        self.run_breathing_led = False
        self.origin_led_status = None
        self.changing_color = False

        self.get_config(config)
        self.register_command(self.gcode)

    def register_command(self, gcode):
        gcode.register_command('BLT', self.cmd_test)
        gcode.register_command('{}_START'.format(self.prefix.upper()),self.cmd_breathing_led)
        gcode.register_command('{}_STOP'.format(self.prefix.upper()),self.cmd_stop)

    def get_config(self, config):
        self.chain_count = config.getint('chain_count', 1, minval=1)
        self.mode = config.get('mode', 'ghost').lower().strip()
        self.led_name = config.get('led_name', None).strip()
        self.led_class = config.get('led_class', None).strip()
        self.fps = config.getint('fps', 24)
        self.name = config.get_name()
        self.prefix = self.name.split(' ')[-1]

        if self.led_name is None or self.led_class is None:
            raise ValueError('led name and class in [breathing led] mush be specified.')

        indices = config.get('indices', '0').split(',')
        self.indices = []
        for index in indices:
            try:
                if '-' in index:
                    start, end = index.split('-')
                    start, end = int(start), int(end)
                    self.indices += [i for i in range(start, end)]
                else:
                    self.indices.append(int(index))
            except:
                raise ValueError('indices value is not correct')
        self.indices = sorted([index for index in self.indices if index < self.chain_count])

        self.color_table = []
        color_table = config.getlist('color_table', None, sep='\n')
        for config in color_table:
            if not config:
                continue
            anchor, color = config.split(':')
            anchor = float(anchor)
            color = color[1:-1].split(',')
            color += ['0.'] * (len(color) - 4)
            self.color_table.append((anchor, [float(c) for c in color]))

        if self.mode == 'echo':
            self.run = self._run_echo
        elif self.mode == 'ghost':
            self.run = self._run_ghost
            self.ghost_breathing_led_helper = GhostBreathingLedHelper(self.color_table)
        else:
            raise ValueError('model "{}" in {} is not supported.'.format(self.mode,self.name))

    def change_color(self, from_color, to_color, sec):
        self.changing_color = True
        times = int(sec * self.fps)
        colors = []
        interval = 1. / self.fps

        for t in range(times):
            color = [max(0, min(1, (to_color[i] - c) / times * t + c)) for i, c in enumerate(from_color)]
            colors.append(color)
            self.set_color(*color)
            time.sleep(interval)

        self.changing_color = False
        return color

    def set_color(self, r, g, b, w='0.'):
        r, g, b, w = str(r), str(g), str(b), str(w)
        cmds = []
        for i,index in enumerate(self.indices):
            params = {'RED': r, 'GREEN': g, 'BLUE': b, 'WHITE': w, 'INDEX': index, 'SYNC': 1, 'TRANSMIT': 0}
            if i == len(self.indices) - 1:
                params['TRANSMIT'] = 1
            else:
                params['TRANSMIT'] = 0
            cmd = self.gcode.create_gcode_command('SET_LED', 'SET_LED', params)
            cmds.append(cmd)

        for cmd in cmds:
            self.led_cmd(cmd)
        return cmd

    def _handle_connect(self):
        self.gcode_move = self.printer.lookup_object('gcode_move')
        self.led_cmd = self.gcode.mux_commands['SET_LED'][1][self.led_name]

        led_class = self.printer.lookup_objects('{}'.format(self.led_class))
        led_object = None
        for led in led_class:
            name,object = led
            name = name.strip()
            if name.endswith(self.led_name) and (led_object is None or len(led_object[0]) > len(name)):
                led_object = led

        if led_object is None:
            raise ValueError('led name "{}" was not found in "{}"'.format(self.led_name,self.led_class))
        else:
            self.led = led_object[1]

        self.led_helper = self.printer.lookup_object('led')

    def _run_ghost(self,gcmd):
        def __run_ghost(self):
            last_speed = self.gcode_move.speed
            last_color = self.origin_led_status[self.indices[0]]
            try:
                while self.run_breathing_led:
                        speed = self.gcode_move.speed
                        if speed != last_speed and not self.changing_color:
                            target_color = self.ghost_breathing_led_helper.get_color(speed)
                            logging.info('s/t:{:.2f}/{:.2f} target color:{}'.format(last_speed,speed,target_color))
                            last_color = self.change_color(last_color,target_color,0.5)
                            last_speed = speed
                        else:
                            time.sleep(0.1)
                self.led.led_helper.update_func(self.origin_led_status, None)
            except Exception as e:
                logging.info('Breathing Led Exception\n{}'.format(str(e)))

        t = Thread(target=__run_ghost,args=(self,))
        t.start()






    def _run_echo(self,*args):
        def __run_echo(self):
            count = 0
            color = self.color_table[count][1]
            last_color = self.color_table[count]
            self.set_color(*color)
            count = (count + 1) % len(self.color_table)
            try:
                while self.run_breathing_led:
                    color = self.color_table[count]
                    if color[1] == last_color[1]:
                        time.sleep(color[0])
                    else:
                        self.change_color(last_color[1], color[1], color[0])
                    count = (count + 1) % len(self.color_table)
                    last_color = color

                self.led.led_helper.update_func(self.origin_led_status, None)
            except Exception as e:
                logging.info('Breathing Led Exception\n{}'.format(str(e)))

        t = Thread(target=__run_echo, args=(self,))
        t.start()

    def run(self,*args,**kwargs):
        pass

    def cmd_stop(self,gcmd):
        self.run_breathing_led = False

    def cmd_breathing_led(self, gcmd):
        if self.run_breathing_led:
            return
        self.run_breathing_led = True
        self.origin_led_status = self.led.get_status()['color_data']
        self.run(gcmd)

    def cmd_test(self, gcmd):
        gcmd.respond_info('chain count:{}'.format(self.chain_count))
        gcmd.respond_info('mode:{}'.format(self.mode))
        gcmd.respond_info('indices:{}'.format(self.indices))
        gcmd.respond_info('get name:{}'.format(self.name))
        gcmd.respond_info('led name:{}'.format(self.led_name))
        gcmd.respond_info('color table:{}'.format(self.color_table))
        gcmd.respond_info('speed:{}'.format(self.gcode_move.speed))


def load_config_prefix(config):
    return BreathingLed(config)
